#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <ctype.h>
#endif
#include <GL/gl.h>
#ifdef _WIN32
#include <GL/wglext.h>
#else
#include <GL/glx.h>
#include "GL/glxext.h"
#endif
#define GLH_EXT_SINGLE_FILE

#define GLH_NAMESPACE_GL   1
#define GLH_NAMESPACE_WGL  2
#define GLH_NAMESPACE_GLX  3

int GetToken(FILE *fpInput, char* token) {
	return fscanf(fpInput, "%s", token);
}

int GetNamespace(const char * token) {
	if(strstr(token, "wgl")) return GLH_NAMESPACE_WGL;
	if(strstr(token, "glX")) return GLH_NAMESPACE_GLX;
	return GLH_NAMESPACE_GL;
}

FILE* OpenFile(const char* filename, const char* perms) {
	FILE *fp;
	fp = fopen(filename, perms);
	if (NULL == fp) {
		printf("ERROR: could not open %s\n", filename);
		exit(-1);
	}
	return fp;
}

int ParseFile(FILE *fpInput, FILE *fpOutput, int header, int footer)
{
    int i;
	char token[256];
	char uprToken[256];
	int tokenNamespace;
	int opengl12names;
	char *name_macro;
	if (footer) {
		fprintf(fpOutput, "#ifndef _WIN32\n");	    	
	}
	if (!header) {
		fprintf(fpOutput, "#ifdef GLH_EXT_SINGLE_FILE\n");
		fprintf(fpOutput, "\n");
		fprintf(fpOutput, "int glh_init_extension(const char* extension)\n");
		fprintf(fpOutput, "{\n");
		fprintf(fpOutput, "    if (NULL == extension) {\n");
		fprintf(fpOutput, "        return GL_FALSE;\n");
		fprintf(fpOutput, "#ifndef _WIN32\n");
		fprintf(fpOutput, "    }else if (0 == strcmp (extension, \"GL_ARB_multitexture\") || 0 == strcmp (extension, \"GL_VERSION_1_2\")) {\n");
		fprintf(fpOutput, "        return GL_TRUE;\n");
		fprintf(fpOutput, "#endif\n");

	} else if (!footer) {
	    
		fprintf(fpOutput, "/* File generated by extgen.cpp -- do not modify */\n");

		fprintf(fpOutput, "#ifndef GLH_GENEXT_H\n");
		fprintf(fpOutput, "#define GLH_GENEXT_H\n\n");
		fprintf(fpOutput, "#ifdef __cplusplus\n");
		fprintf(fpOutput, "extern \"C\" {\n");
   		fprintf(fpOutput, "#endif\n\n");
		fprintf(fpOutput, "#include <GL/gl.h>\n");
		fprintf(fpOutput, "#include <GL/glext.h>\n");
		fprintf(fpOutput, "#ifdef _WIN32 /* supports windows, x -- need to generalize */\n");
		fprintf(fpOutput, "#  include <GL/wglext.h>\n");
		fprintf(fpOutput, "#  define GLH_EXT_GET_PROC_ADDRESS(p)   wglGetProcAddress(p) \n");
		fprintf(fpOutput, "#elif GLX_VERSION_1_3\n");
		fprintf(fpOutput, "#  include <string.h>\n");
		fprintf(fpOutput, "#  include <GL/glx.h>\n");
		fprintf(fpOutput, "#  include <GL/glxext.h>\n");
		fprintf(fpOutput, "#  define GLH_EXT_GET_PROC_ADDRESS(p)   glXGetProcAddressARB( (const GLubyte *) p) \n");
		fprintf(fpOutput, "#endif\n\n");

		fprintf(fpOutput, "#ifdef GLH_EXT_SINGLE_FILE\n");
		fprintf(fpOutput, "# define GLH_EXTERN\n");
		fprintf(fpOutput, "# define GLH_INITIALIZER = 0\n");
		fprintf(fpOutput, "#else\n");
		fprintf(fpOutput, "# define GLH_EXTERN extern\n");
		fprintf(fpOutput, "# define GLH_INITIALIZER\n");
		fprintf(fpOutput, "#endif\n\n");


        fprintf(fpOutput, "#define GLH__PREPROCESSOR_GYMNASTICS2(a,b) a##b\n");
		fprintf(fpOutput, "#define GLH__PREPROCESSOR_GYMNASTICS(a,b) GLH__PREPROCESSOR_GYMNASTICS2(a,b)\n\n");
		fprintf (fpOutput, "#ifndef _WIN32\n");
		fprintf (fpOutput, "#define GLH_EXT_PREFIX _\n");
		fprintf (fpOutput, "#endif\n");
		

		fprintf(fpOutput, "#ifndef GLH_EXT_PREFIX\n");
		fprintf(fpOutput, "# define GLH_EXT_NAME(a) a\n");
		fprintf(fpOutput, "#else\n");
		fprintf(fpOutput, "# define GLH_EXT_NAME(a) GLH__PREPROCESSOR_GYMNASTICS(GLH_EXT_PREFIX,a)\n");
		fprintf(fpOutput, "#endif\n\n");

		fprintf(fpOutput, "#ifndef _WIN32\n");
		fprintf(fpOutput, "# ifndef GLH_CORE_1_2_PREFIX\n");
		fprintf(fpOutput, "#  define GLH_CORE_1_2_PREFIX _\n");
		fprintf(fpOutput, "# endif\n");
		fprintf(fpOutput, "#endif\n\n");

		fprintf(fpOutput, "#ifndef GLH_CORE_1_2_PREFIX\n");
		fprintf(fpOutput, "# define GLH_CORE_1_2_NAME(a) a\n");
		fprintf(fpOutput, "#else\n");
		fprintf(fpOutput, "# define GLH_CORE_1_2_NAME(a) GLH__PREPROCESSOR_GYMNASTICS(GLH_CORE_1_2_PREFIX,a)\n");
		fprintf(fpOutput, "#endif\n\n");



	}


A:
	if (GetToken(fpInput, token) == EOF) goto FIN;
	if (0 == strcmp(token, "GL_VERSION_1_2")) {
		opengl12names = 1;
		name_macro = "GLH_CORE_1_2_NAME";
	} else {
	    if (0 == strcmp(token, "GL_ARB_multitexture")) {
		opengl12names=2;
		name_macro = "GLH_EXT_NAME";
	    } else 
	    {
		opengl12names = 0;
		name_macro = "GLH_EXT_NAME";
	    }
	}
	if (opengl12names>0)
	    fprintf (fpOutput, "#ifdef _WIN32\n");
	fprintf(fpOutput, "#ifdef %s\n", token);
	if (!header) {
		fprintf(fpOutput, "    } else if (0 == strcmp(extension, \"%s\")) {\n", token);
	} else {
		if (opengl12names==1) {
			fprintf(fpOutput, "    /* These routines are prefixed by the preprocessor constant\n");
			fprintf(fpOutput, "       GLH_CORE_1_2_PREFIX to avoid colliding with the OpenGL 1.2 namespace. */\n");
		}
	}
	goto B;
B:
	if (GetToken(fpInput, token) == EOF) goto ERR;
	if (token[0] == '{') goto C;
	goto ERR;
C:
	if (GetToken(fpInput, token) == EOF) goto ERR;
	if (token[0] == '}') {
	    if (opengl12names)
		fprintf (fpOutput, "#endif\n");
	    fprintf(fpOutput, "#endif\n\n");
	    goto A;
	}
	strcpy(uprToken, token);
	for (i=0;uprToken[i]!='\0';i++) {
	  uprToken[i] = toupper(uprToken[i]);
	}
	tokenNamespace = GetNamespace(token);
	if (tokenNamespace == GLH_NAMESPACE_WGL) {
		fprintf(fpOutput, "# ifdef _WIN32\n");
	} else if (tokenNamespace == GLH_NAMESPACE_GLX) {
		fprintf(fpOutput, "# ifdef GLX_VERSION_1_3\n");
	}
	if (header) {
	    if (!footer) {
		fprintf(fpOutput, "    GLH_EXTERN PFN%sPROC %s(%s) GLH_INITIALIZER;\n", uprToken, name_macro, token);
	    }else {
		fprintf(fpOutput, "#define %s %s(%s)\n", token, name_macro, token);
	    }
	} else {
		fprintf(fpOutput, "        %s(%s) = (PFN%sPROC)GLH_EXT_GET_PROC_ADDRESS(\"%s\");\n", name_macro, token, uprToken, token);
		fprintf(fpOutput, "        if (NULL == %s(%s))\n", name_macro, token);
		fprintf(fpOutput, "            return GL_FALSE;\n");
	}
	if (tokenNamespace != GLH_NAMESPACE_GL) {
		fprintf(fpOutput, "# endif\n");
	}
	goto C;
FIN:
	if (header) {
	    fprintf(fpOutput, "\n");
	    if (footer)
		fprintf(fpOutput, "#endif\n");	    
	} else {
		fprintf(fpOutput, "    } else {\n");
		fprintf(fpOutput, "        return GL_FALSE;\n");
		fprintf(fpOutput, "    }\n");
		fprintf(fpOutput, "    return GL_TRUE;\n");
		fprintf(fpOutput, "}\n");
		fprintf(fpOutput, "#endif\n\n");
		fprintf(fpOutput, "#undef GLH_EXT_SINGLE_FILE\n\n");
		fprintf(fpOutput, "#ifdef __cplusplus\n");
		fprintf(fpOutput, "}\n");
		fprintf(fpOutput, "#endif\n\n");
		fprintf(fpOutput, "#endif /* GLH_GENEXT_H */\n");
	}
	return 0;
ERR:
	printf("Error!\n");
	return 1;
}

int main(int argc, char** argv)
{
	FILE *fpInput, *fpOutput;
	char tmp1[] = "glh_genext.h";
	char tmp2[] = "extfile.txt";
	char* outputFilename = (argc > 1) ? argv[1] : tmp1;
	char* inputFilename = (argc > 2) ? argv[2] : tmp2;
	fpOutput = OpenFile(outputFilename, "w");

	/* Parse file for header portion */
	fpInput = OpenFile(inputFilename, "r");
	ParseFile(fpInput, fpOutput, 1,0);
	fclose(fpInput);

	/* Parse file again for code portion */
	fpInput = OpenFile(inputFilename, "r");
	ParseFile(fpInput, fpOutput, 0,0);
	fclose(fpInput);

	fpInput = OpenFile(inputFilename, "r");
	ParseFile(fpInput, fpOutput, 1,1);
	fclose(fpInput);

	fclose(fpOutput);

	return 0;
}
